#region Imports

using System;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.InteropServices;
using System.Runtime.Remoting;
using Sedna.API.Remoting;
using log4net;
using Spring.Objects.Factory;
using Spring.Objects.Factory.Support;
using Spring.Proxy;

#endregion

namespace Sedna.API.Remoting
{
    /// <summary>
    /// Publishes an instance of an object under
    /// a given url as a Server Activated Object (SAO).
    /// </summary>
    public class SaoExporter : IInitializingObject, IDisposable, ISaoExporter 
    {
        private ILog log = LogManager.GetLogger(typeof(SaoExporter));
        
        public class EternalMarshalByRefObject: MarshalByRefObject
        {
            public override object InitializeLifetimeService()
            {
                return null;
            }
        }


        private const string PROXY_TYPE_NAME_TEMPLATE = "Sedna.API.Remoting.Proxy.Proxy_{0}";
        
        private static AssemblyBuilder assembly;

        private Type targetInterface;
        private string applicationName;
        private string serviceName;
        private object target;
        private MarshalByRefObject proxy;
        private ObjRef objRef;


        /// <summary>
        /// Initializes assembly and module builder that will be used
        /// to dynamically create remote object wrappers.
        /// </summary>
        static SaoExporter()
        {
            AssemblyName an = new AssemblyName();
            an.Name = "Sedna.API.Remoting.Proxy";
            assembly = AppDomain.CurrentDomain.DefineDynamicAssembly(an, AssemblyBuilderAccess.Run);
        }

        /// <summary>
        /// Creates a new instance of the SAO exporter.
        /// </summary>
        public SaoExporter()
        { }

        #region Properties

        public Type TargetInterface
        {
            get { return targetInterface; }
            set { targetInterface = value; }
        }

        /// <summary>
        /// Gets or sets the name of the remote application.
        /// </summary>
        public string ApplicationName
        {
            get { return applicationName; }
            set { applicationName = value; }
        }

        /// <summary>
        /// Gets or sets the name of the exported remote service.
        /// </summary>
        public string ServiceName
        {
            get { return serviceName; }
            set { serviceName = value; }
        }


        public object Target
        {
            get { return target; }
            set { target = value; }
        }

        public ObjRef ObjRef
        {
            get { return objRef; }
        }

        public MarshalByRefObject Proxy
        {
            get { return proxy; }
        }

        #endregion

        /// <summary>
        /// Called by application context after properties are initialized.
        /// </summary>
        public void AfterPropertiesSet()
        {
            Export();
        }

        public void Dispose()
        {
            log.Info("Disconnecting object with name " + GetName());
            RemotingServices.Disconnect(proxy);
        }

        /// <summary>
        /// Exports specified target as a SAO object.
        /// </summary>
        public void Export()
        {
            SaoProxyTypeBuilder builder = new SaoProxyTypeBuilder();
            builder.TargetType = target.GetType();
            builder.Interfaces = new Type[] {targetInterface};
            builder.BaseType = typeof(EternalMarshalByRefObject);
            builder.Name = String.Format(PROXY_TYPE_NAME_TEMPLATE, Guid.NewGuid().ToString("N"));
            
            Type remoteObjectType = builder.BuildProxyType();
            ConstructorInfo constructor = remoteObjectType.GetConstructor(new Type[0]);
            proxy = (MarshalByRefObject)constructor.Invoke(new object[0]);
            SaoProxyTypeBuilder.SetTarget(proxy, target);

            
            string name = GetName();
            log.Info("Exporting " + remoteObjectType.FullName + " as " + name);
            objRef = RemotingServices.Marshal(proxy, name);
        }

        private string GetName()
        {
            return (applicationName != null ? applicationName + "/" + serviceName : serviceName);
        }
    }
}